package com.cmft.slas.es.config;

import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.floragunn.searchguard.ssl.SearchGuardSSLPlugin;
import com.floragunn.searchguard.ssl.util.SSLConfigConstants;
import lombok.extern.slf4j.Slf4j;
import lombok.val;
import org.apache.commons.io.IOUtils;
import org.elasticsearch.client.transport.TransportClient;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.transport.TransportAddress;
import org.elasticsearch.transport.client.PreBuiltTransportClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.Resource;
import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
import org.springframework.data.elasticsearch.core.EntityMapper;
import org.springframework.data.elasticsearch.core.geo.CustomGeoModule;
import org.springframework.data.elasticsearch.repository.config.EnableElasticsearchRepositories;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.InetAddress;
import java.net.URL;

/**
 * @author zhangf002(张帆)
 * @since 2019-06-12 11:28
 */
@Slf4j
@ConditionalOnProperty(
        prefix = "elasticsearch",
        value = {"cluster-name", "cluster-nodes"})
@EntityScan("com.cmft.slas.es.domain")
@EnableElasticsearchRepositories("com.cmft.slas.es.repository")
@ComponentScan("com.cmft.slas.es.service")
@EnableConfigurationProperties(ElasticsearchProperties.class)
@Configuration
public class ElasticSearchConfiguration {

    @Autowired
    private ElasticsearchProperties properties;

    @Bean
    public ElasticsearchTemplate elasticsearchTemplate() throws IOException {
        return new ElasticsearchTemplate(transportClient(), esEntityMapper());
    }

    @Bean
    public EntityMapper esEntityMapper() {
        return new CustomEntityMapper();
    }

    @Bean
    public TransportClient transportClient() throws IOException {
        val settings = settings();

        log.info("准备初始化elasticsearch客户端, 配置:{}", settings);

        val client = new PreBuiltTransportClient(settings, SearchGuardSSLPlugin.class);

        for (String node : properties.getClusterNodes()) {
            String[] splits = node.split(":");
            val ip = InetAddress.getByName(splits[0]);
            val port = Integer.parseInt(splits[1]);
            client.addTransportAddress(new TransportAddress(ip, port));
            log.info("添加elasticsearch客户端节点:"+node);
        }

        log.info("elasticsearch客户端初始化成功");
        return client;
    }

    private Settings settings() throws IOException {
        String keystore, truststore;

        if (isInsideJar(properties.getKeystore())) {
            keystore = createTempFile("keystore.jks", properties.getKeystore().getInputStream());
        } else {
            keystore = properties.getKeystore().getFile().getPath();
        }

        if (isInsideJar(properties.getTruststore())) {
            truststore = createTempFile("truststore.jks", properties.getTruststore().getInputStream());
        } else {
            truststore = properties.getTruststore().getFile().getPath();
        }

        return Settings.builder()
                       .put("path.home", ".")
                       .put("cluster.name", properties.getClusterName())
                       .put(SSLConfigConstants.SEARCHGUARD_SSL_HTTP_ENABLED, false)
                       .put(SSLConfigConstants.SEARCHGUARD_SSL_TRANSPORT_KEYSTORE_FILEPATH, keystore)
                       .put(SSLConfigConstants.SEARCHGUARD_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, truststore)
                       .put(SSLConfigConstants.SEARCHGUARD_SSL_TRANSPORT_KEYSTORE_PASSWORD, properties.getKeystorePassword())
                       .put(SSLConfigConstants.SEARCHGUARD_SSL_TRANSPORT_TRUSTSTORE_PASSWORD, properties.getTruststorePassword())
                       .put("searchguard.ssl.transport.enforce_hostname_verification", false)
                       .build();
    }

    private static boolean isInsideJar(Resource resource) throws IOException {
//        log.debug("isInsideJar: {}", resource.getURL().getPath());
        return resource.getURL().getPath().contains("jar!");
    }

    private static String createTempFile(String fileName, InputStream is) {
        String tmpDir = System.getProperty("java.io.tmpdir");
        String tempFilePath = tmpDir + File.separator + fileName;
        try (val os = new FileOutputStream(tempFilePath)) {
            IOUtils.copy(is, os);
        } catch (IOException e) {
            log.error("拷贝jks证书文件到临时目录({})异常：{}", tmpDir, e.getMessage());
            return null;
        }
        return tempFilePath;
    }

    private static class CustomEntityMapper implements EntityMapper {

        private final ObjectMapper objectMapper = new ObjectMapper()
                .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
                .configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true)
                .configure(DeserializationFeature.READ_DATE_TIMESTAMPS_AS_NANOSECONDS, false)
                .registerModule(new CustomGeoModule())
                .registerModule(new JavaTimeModule());

        @Override
        public String mapToString(Object object) throws IOException {
            return objectMapper.writeValueAsString(object);
        }

        @Override
        public <T> T mapToObject(String source, Class<T> clazz) throws IOException {
            return objectMapper.readValue(source, clazz);
        }
    }
}
